package com.rybbaby.tss.core.sql.metadata;

import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.mapping.ForeignKey;
import org.jboss.logging.Logger;

public class TableMetadata {
	
	private static final CoreMessageLogger LOG = (CoreMessageLogger)Logger.getMessageLogger(CoreMessageLogger.class , TableMetadata.class.getName());
	/** catalog **/
	private String catalog;
	/** schema **/
	private String schema;
	/** 表名 **/
	private String name;
	/** 备注 **/
	private String remarks;
	/** 主键列集合 **/
	private List<ColumnMetadata> primaryKeys = new ArrayList<ColumnMetadata>();
	private List<String> primaryKeysStr = new ArrayList<String>();
	/** 所有列集合 **/
	private Map<String , ColumnMetadata> columns = new HashMap<String , ColumnMetadata>();
	/** 外键集合 **/
	private Map<String , ForeignKeyMetadata> foreignKeys = new HashMap<String , ForeignKeyMetadata>();
	/** 索引集合 **/
	private Map<String , IndexMetadata> indexes = new HashMap<String , IndexMetadata>();
	
	public TableMetadata (ResultSet rs , DatabaseMetaData meta , boolean extras ) throws SQLException{
		// {.REF_GENERATION=9, .REMARKS=4, .SELF_REFERENCING_COL_NAME=8,
		// .TABLE_CAT=0, .TABLE_NAME=2, .TABLE_SCHEM=1, .TABLE_TYPE=3,
		// .TYPE_CAT=5, .TYPE_NAME=7, .TYPE_SCHEM=6}
		this.catalog = rs.getString("TABLE_CAT");
		this.schema = rs.getString("TABLE_SCHEM");
		this.name = rs.getString("TABLE_NAME");
		this.remarks = rs.getString("REMARKS");
		initColumns(meta);
		initPrimaryKeys(meta);
		if(extras) {
			initForeignKeys(meta);
			initIndexes(meta);
		}
		String cat = this.catalog + '.';
		String schem = this.schema + '.';
		LOG.tableFound(cat + schem + this.name);
		LOG.columns(this.columns.keySet());
		if(extras) {
			LOG.foreignKeys(this.foreignKeys.keySet());
			LOG.indexes(this.indexes.keySet());
		}
	}
	
	public String getName() {
		return this.name;
	}
	
	public String getCatalog() {
		return this.catalog;
	}
	
	public String getSchema() {
		return this.schema;
	}
	
	public String getRemarks() {
		return remarks;
	}
	
	public void setRemarks(String remarks) {
		this.remarks = remarks;
	}
	
	public Map<String , ColumnMetadata> getColumns() {
		return columns;
	}
	
	public List<ColumnMetadata> getPrimaryKeys() {
		return primaryKeys;
	}
	
	public List<String> getPrimaryKeysStr() {
		return primaryKeysStr;
	}
	
	public Map<String , ForeignKeyMetadata> getForeignKeys() {
		return foreignKeys;
	}
	
	public Map<String , IndexMetadata> getIndexes() {
		return indexes;
	}
	
	public ColumnMetadata getColumnMetadata(String columnName) {
		return this.columns.get(columnName.toLowerCase());
	}
	
	public ForeignKeyMetadata getForeignKeyMetadata(String keyName) {
		return this.foreignKeys.get(keyName.toLowerCase());
	}
	
	public ForeignKeyMetadata getForeignKeyMetadata(ForeignKey fk) {
		Iterator<ForeignKeyMetadata> it = this.foreignKeys.values().iterator();
		while (it.hasNext()) {
			ForeignKeyMetadata existingFk = it.next();
			if(existingFk.matches(fk)) {
				return existingFk;
			}
		}
		return null;
	}
	
	public IndexMetadata getIndexMetadata(String indexName) {
		return this.indexes.get(indexName.toLowerCase());
	}
	
	public void addColumn(ResultSet rs) throws SQLException {
		String column = rs.getString("COLUMN_NAME");
		if(column == null) {
			return;
		}
		if(getColumnMetadata(column) == null) {
			ColumnMetadata info = new ColumnMetadata(rs);
			this.columns.put(info.getName().toLowerCase() , info);
		}
	}
	
	public String toString() {
		return "TableMetadata(" + this.name + ')';
	}
	
	private void addForeignKey(ResultSet rs) throws SQLException {
		String fk = rs.getString("FK_NAME");
		if(fk == null) {
			return;
		}
		ForeignKeyMetadata info = getForeignKeyMetadata(fk);
		if(info == null) {
			info = new ForeignKeyMetadata(rs);
			this.foreignKeys.put(info.getName().toLowerCase() , info);
		}
		info.addReference(rs);
	}
	
	private void addPrimaryKey(ResultSet rs) throws SQLException {
		String columnName = rs.getString("COLUMN_NAME");
		if(StringUtils.isNotBlank(columnName)) {
			ColumnMetadata columnMetadata = this.columns.get(columnName);
			if(columnMetadata != null && !this.primaryKeys.contains(columnMetadata)) {
				this.primaryKeys.add(columnMetadata);
				this.primaryKeysStr.add(columnMetadata.getName().toLowerCase());
			}
		}
	}
	
	private void addIndex(ResultSet rs) throws SQLException {
		String index = rs.getString("INDEX_NAME");
		if(index == null) {
			return;
		}
		IndexMetadata info = getIndexMetadata(index);
		if(info == null) {
			info = new IndexMetadata(rs);
			this.indexes.put(info.getName().toLowerCase() , info);
		}
		info.addColumn(getColumnMetadata(rs.getString("COLUMN_NAME")));
	}
	
	private void initForeignKeys(DatabaseMetaData meta) throws SQLException {
		ResultSet rs = null;
		try {
			rs = meta.getImportedKeys(this.catalog , this.schema , this.name);
			while (rs.next()) {
				addForeignKey(rs);
			}
		} finally {
			closeResultSet(rs);
		}
	}
	
	private void initIndexes(DatabaseMetaData meta) throws SQLException {
		ResultSet rs = null;
		try {
			rs = meta.getIndexInfo(this.catalog , this.schema , this.name , false , true);
			while (rs.next()) {
				if(rs.getShort("TYPE") == 0) {
					continue;
				}
				addIndex(rs);
			}
		} finally {
			closeResultSet(rs);
		}
	}
	
	private void initColumns(DatabaseMetaData meta) throws SQLException {
		ResultSet rs = null;
		try {
			rs = meta.getColumns(this.catalog , this.schema , this.name , "%");
			while (rs.next())
				addColumn(rs);
		} finally {
			closeResultSet(rs);
		}
	}
	
	private void initPrimaryKeys(DatabaseMetaData meta) throws SQLException {
		ResultSet rs = null;
		try {
			rs = meta.getPrimaryKeys(catalog , schema , this.name);
			while (rs.next())
				addPrimaryKey(rs);
		} finally {
			closeResultSet(rs);
		}
	}
	
	private void closeResultSet(ResultSet rs) throws SQLException {
		if(rs != null) {
			rs.close();
		}
	}
}